﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Operators;
using Weazel.Badger.Vhdl.Statements;
using Weazel.Badger.Vhdl.Expressions;

#endregion

namespace Weazel.Badger
{
  public abstract class SystemEntity : Entity
  {
		protected ConversionContext context;
    protected Gezel.Model.Model model;
    protected Gezel.Model.system system;

    public SystemEntity(ConversionContext context, Gezel.Model.Model model, Gezel.Model.system system) : base(system.LocalName)
    {
			this.context = context;
      this.model = model;
      this.system = system;
    }
   
    protected void CreateSignals()
    {
      foreach (Gezel.Model.system.Net net in system.Nets.Values)
      {        
        if (net.OutPort == null)
        {
          Console.ForegroundColor = ConsoleColor.Cyan;
          Console.WriteLine("un-bound output found for net: " + net.LocalName);
          Console.ResetColor();
          
          OnUnconnectedOutputNetFound(net);
          continue;
        }

        int maxWidth = net.OutPort.Width;

        if (net.InPorts.Count == 0)
        {
          Console.ForegroundColor = ConsoleColor.Cyan;
          Console.WriteLine("un-bound input found for net: " + net.LocalName);
          Console.ResetColor();

          // no sources defined for this output ..
          OnUnconnectedInputNetFound(net);
        }
        else
        {
          foreach (Weazel.Gezel.Model.InPort inPort in net.InPorts)
            if (inPort.Width > maxWidth)
              maxWidth = inPort.Width;

          Vhdl.Types.Type type = maxWidth == 1 ?
              (Vhdl.Types.Type)new StdLogic()
            : (Vhdl.Types.Type)new StdLogicVector(maxWidth);

          Signal signal = new Signal(type, new GezelType(net.InPorts[0].Signed, maxWidth), net.LocalName);
          signal.Type = type;

          AddSignal(signal);
          AddDeclarativeItem(signal);
        }
      }
    }

    private Vhdl.Types.Type getType(int width)
    {
      return width == 1 ?
          (Vhdl.Types.Type)new StdLogic()
        : (Vhdl.Types.Type)new StdLogicVector(width);
    }

		/// <summary>
		/// Create the 'component' declaration in the entity. Each datapath/controller
		/// pair referenced has exactly one of these
		/// </summary>
		/// <param name="clock">the clock signal</param>
		/// <param name="reset">the reset signal</param>
    protected void CreateComponentDeclarations(Signal clock, Signal reset)
    {
			string[] referencedDatapaths = system.GetDatapathReferenceNamed();

      foreach (string datapathName in referencedDatapaths)
      {
				ComponentDeclaration component =
					context.Datapaths.ContainsKey(datapathName) ? context.Datapaths[datapathName].GetDeclaration() :
					context.IpBlocks.ContainsKey(datapathName) ? context.IpBlocks[datapathName] :
					null;

				if (component == null)
				{
					string message =
						string.Format("Failed to locate datapath or IP block '{0}' in model", datapathName);

					throw new VhdlModelException(message);
				}

				// only add this declaration if it does not already exist
				bool addComponent = true;

				foreach (Vhdl.IDeclarativeItem item in DeclarativeItems)
					if (item is Vhdl.ComponentDeclaration && ((Vhdl.ComponentDeclaration)item).CompareTo(component))
						addComponent = false;

				if (addComponent)
					AddDeclarativeItem(component);
      }
    }

    protected abstract void OnUnconnectedInputNetFound(Gezel.Model.system.Net net);
    protected abstract void OnUnconnectedOutputNetFound(Gezel.Model.system.Net net);

		/// <summary>
		/// This method creates the necessary portmaps based
		/// on the information in the "system" module
		/// </summary>
		/// <param name="clock"></param>
		/// <param name="reset"></param>
    protected void CreatePortMaps(Signal clock, Signal reset)
    { 
			// lists of nets. Nets will be removed when when a source
			// or sink is discovered.
      List<Gezel.Model.system.Net> unconnectedInputNets = 
				new List<Weazel.Gezel.Model.system.Net>();

      List<Gezel.Model.system.Net> unconnectedOutputNets = 
				new List<Weazel.Gezel.Model.system.Net>();

			// copy to lists ..
      foreach (Gezel.Model.system.Net net in system.Nets.Values)
      {
        unconnectedInputNets.Add(net);
        unconnectedOutputNets.Add(net);
      }

			string[] referencedDatapaths = system.GetDatapathReferenceNamed();
		
      foreach (string dpName in referencedDatapaths)
      {
        bool isDatapath = context.Datapaths.ContainsKey(dpName);
        bool isIpBlock = context.IpBlocks.ContainsKey(dpName);

				ComponentDeclaration component =
						isDatapath ? context.Datapaths[dpName].GetDeclaration()
					: isIpBlock ? context.IpBlocks[dpName]
					: null;
								
				PortMap portMap = new PortMap("label_" + dpName, component.Name);

				// get an ordered list of nets connected to this block
				string[] positionalBindingList = 
					system.GetPositionalBindingList(dpName);

				// foreach port on component minus clock and reset
				for (int i = 0; i < component.Count - 2; i++)
				{
					// get a reference to the connection at position i
					Weazel.Gezel.Model.system.DatapathConnectionInfo connection = 
						system.PortConnections[positionalBindingList[i]];

					// add to port map
					portMap.Add(component.PortNameAtPosition(i), connection.LocalName);

					if (component.DirectionAtPosition(i) == PortDirection.In)
					{						
					}
					else // must be out port
					{						
					}
				}
				
        if(isDatapath)
        {          
          // use the divide clock always
          portMap.Add(context.Datapaths[dpName].ClockPort.Name, context.Configuration.ClockName);
          portMap.Add(context.Datapaths[dpName].ResetPort.Name, reset.Name);
        } 
        else if(isIpBlock)
        {
          // TODO: these names may not always be correct..
          //       the clock/reset names of IP blocks are unknown
          portMap.Add("clock", context.Configuration.ClockName);
          portMap.Add("reset", reset.Name);               
        }

        AddPortMap(portMap);
      }

      if (unconnectedInputNets.Count > 0)
      {
        Console.WriteLine("No definers found for nets:");

        foreach (Gezel.Model.system.Net net in unconnectedInputNets)
        {
					string message = 
						string.Format("\t{0} source: {1}", net.LocalName, net.OutPort);

          Console.WriteLine(message);
        }
      }

      if (unconnectedOutputNets.Count > 0)
      {
        Console.WriteLine("No sources found for nets:");

        foreach (Gezel.Model.system.Net net in unconnectedOutputNets)
        {
          Console.WriteLine("\t" + net.LocalName);
        }
      }
    }

    protected void CreateClockAndResetGenerationProcess(Signal clock, Signal reset)
    {
      Process resetProcess = new Process("reset_generation", this);

      // set reset = 1
      resetProcess.Statements.Add(
        new SignalAssignmentStatement(					
          reset, 
          new Vhdl.Expressions.ConstantExpression(
						resetProcess.Statements,
            "1", 
            new StdLogic(),
            null
          )
        )
      );

      // wait 50 ns
      resetProcess.Statements.Add(new WaitStatement(50));

      // set reset = 0
      resetProcess.Statements.Add(
        new SignalAssignmentStatement(
          reset,
          new Vhdl.Expressions.ConstantExpression(
						resetProcess.Statements,
            "0",
            new StdLogic(),
            null
            )
          )
        );

      resetProcess.Statements.Add(new WaitStatement());

      AddProcess(resetProcess);

      Process clockProcess = new Process("clock_generation", this);
      clockProcess.AddSignalToSensivitityList(clock);

      IfStatement ifStatement = new IfStatement();

      BinaryExpression clockEqlOneExpression =
        new BinaryExpression(
					resetProcess.Statements,
          BinaryOperators.Equals,
          new SignalReferenceExpression(resetProcess.Statements, clock),
          new ConstantExpression(resetProcess.Statements, "'1'", new StdLogic(), null),
          new Vhdl.Types.Boolean(),
          null
        );

      AttributeExpression clocksEventExpression =
        new AttributeExpression(resetProcess.Statements, clock.EventAttribute);

      BinaryExpression andExpression =
        new BinaryExpression(
					resetProcess.Statements,
          BinaryOperators.And,
          clocksEventExpression,
          clockEqlOneExpression,
          new Vhdl.Types.Boolean(),
          null
        );

      ifStatement.Condition = andExpression;

      // set clock = '0' after 25 ns
      ifStatement.TrueStatements.Add(
        new SignalAssignmentStatement(
          clock,
          new Vhdl.Expressions.ConstantExpression(
						ifStatement.TrueStatements,
            "0",            
            new StdLogic()          ,
            null
          ),
         25
        )
       );

      // set clock = '1'
      ifStatement.FalseStatements.Add(
        new SignalAssignmentStatement(
          clock,
          new Vhdl.Expressions.ConstantExpression(
						ifStatement.FalseStatements,
            "1",     
            new StdLogic(),
            null
          ),             
          25
        )
      );

      clockProcess.Statements.Add(ifStatement);

      AddProcess(clockProcess);
    }
  }
}
